/******************************************************************
 * Copyright @ 2024 广州牛师帮科技文化有限公司. All rights reserverd.
 * @file   src\component\Toast\index.tsx
 * @author Gim
 * @date   2024/08/13 16:59
 * @desc   message 的封装，用于减少相同内容的toast输出
 ******************************************************************/

import { Badge, Flex, message } from "antd";
import { ArgsProps, ConfigOptions, JointContent, MessageInstance, MessageType } from "antd/es/message/interface";
import { BadgeProps } from "antd/lib";
import { isError, isFunction, isNumber, isObject, isString } from "lodash";
import React, { createContext, useContext, useMemo } from "react";

export namespace Toast {
  export type MessageTypeOpen = (
    content: Error | JointContent | unknown | undefined | null,
    duration?: number | VoidFunction,
    onClose?: VoidFunction
  ) => MessageType;

  export interface IToastInstance extends Omit<MessageInstance, 'error' | 'warning'> {
    info: MessageTypeOpen;
    success: MessageTypeOpen;
    error: MessageTypeOpen;
    warning: MessageTypeOpen;
  }

  export interface ProviderProps extends React.PropsWithChildren, ConfigOptions { }
  export function Provider(props: ProviderProps) {
    const { children, ...conf } = props
    const [inner, message_ctx] = message.useMessage(conf);
    const toast = useMemo(() => wrap(inner, conf), [inner, conf.duration, conf.maxCount, conf.prefixCls, conf.rtl, conf.top, conf.transitionName])
    return (
      <>
        {message_ctx}
        <toast_ctx.Provider value={toast}>
          {children}
        </toast_ctx.Provider>
      </>
    )
  }

  export function use(): [IToastInstance] {
    const toast = useContext(toast_ctx)
    return [toast]
  }

  const e_msg_type = (): MessageType => {
    const ret: MessageType = function () { }
    ret.then = Promise.resolve(true).then;
    return ret
  }
  type Method = 'info' | 'success' | 'error' | 'warning' | 'loading';
  const badge_colors: Record<Method, BadgeProps['color']> = {
    'info': 'geekblue',
    'success': 'green',
    'error': 'red',
    'warning': 'yellow',
    'loading': 'blue'
  }
  const is_ArgsProps = (v: any): v is ArgsProps => isObject(v) && ('content' in v);
  const get_content = (v: any) => {
    if (isString(v) || isNumber(v) || React.isValidElement(v)) return v;
    if (is_ArgsProps(v)) return isError(v.content) ? v.content.message : v.content;
    return void 0;
  }

  const wrap = (inner: MessageInstance, conf?: ConfigOptions): IToastInstance => {
    const tid_map = new Map<string, number>()
    const count_map = new Map<string, number>()
    const content_map = new Map<string, any>();

    const add_count = (toast_key: string, content: any) => {
      if (content && content_map.get(toast_key) !== content) {
        content_map.set(toast_key, content)
        count_map.set(toast_key, 1)
        return 1
      }
      const count = 1 + (count_map.get(toast_key) ?? 0)
      count_map.set(toast_key, count)
      return count;
    }
    const del_count = (toast_key: string) => {
      count_map.delete(toast_key)
    }
    const to_inner = (method: Method, ...[content, duration, onClose]: Parameters<MessageTypeOpen>) => {
      if (!content) return e_msg_type();
      if (isError(content)) content = content.message;
      if (React.isValidElement(content) && !content.key) return inner[method](content, duration, onClose)
      if (!isString(content) && !isNumber(content) && !is_ArgsProps(content) && !React.isValidElement(content))
        return e_msg_type();

      const toast_key = is_ArgsProps(content) || React.isValidElement(content) ? ('' + content.key) : (method + content)
      const count = add_count(toast_key, content);
      inner.destroy(toast_key);
      const real_content = (count <= 1) ? get_content(content) :
        <Flex align='center' gap={5}>
          {get_content(content)}
          <Badge count={count} size='small' color={badge_colors[method]} />
        </Flex>

      const default_duration = conf?.duration ?? 3;
      const real_on_close = isFunction(duration) ? duration : isFunction(onClose) ? onClose : void 0
      const real_duration = isNumber(duration) ? duration : default_duration;
      const tid = tid_map.get(toast_key)
      if (tid) window.clearTimeout(tid);

      tid_map.set(
        toast_key,
        window.setTimeout(() => {
          del_count(toast_key)
          inner.destroy(toast_key)
        }, real_duration * 1000)
      )

      return inner[method]({
        key: toast_key,
        content: real_content,
      }, 0, real_on_close)
    }
    const inst: IToastInstance = {
      info: to_inner.bind(inner, 'info'),
      success: to_inner.bind(inner, 'success'),
      error: to_inner.bind(inner, 'error'),
      warning: to_inner.bind(inner, 'warning'),
      loading: to_inner.bind(inner, 'loading'),
      open: inner.open.bind(inner),
      destroy: inner.destroy.bind(inner)
    }
    return inst;
  }
  const toast_ctx = createContext<IToastInstance>(wrap(message))
}